package com.nth.android.image;

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import android.widget.ImageView;

import com.nth.android.CacheMap;
import com.nth.android.Util;

/**
 * Title: AndroidFramework <br />
 * Copyright: Copyright (c) 2009 <br />
 * NTH Company: NTH d.o.o. <br />
 * 
 * @author Tomica Bosnjak
 * @version 1.0.0
 * @since 1.0.0
 */
public class Ictest {

	private static final int CACHE_SIZE = 100;
	private static final boolean CACHE_MODE = true;

	private Map<String, Bitmap> urlToBitmap = new CacheMap<String, Bitmap>(Ictest.CACHE_SIZE, Ictest.CACHE_MODE);
	private Queue<Group> queue = new LinkedList<Group>();

	private DownloadThread downloadThread = null;
	private Bitmap missing = null;
	private Bitmap loading = null;
	private boolean busy = false;

	protected int connTimeout = Util.HTTP_CONNECT_TIMEOUT;
	protected int readTimeout = Util.HTTP_READ_TIMEOUT;

	private static Ictest instance = null;

	/**
	 * 
	 * get ImageLoader instance, if null creates new ImageLoader
	 * 
	 * @return instance
	 */
	public static Ictest getInstance() {
		if (instance == null) {
			instance = new Ictest();
		}
		return instance;
	}

	private Ictest() {
		super();
	}

	/**
	 * 
	 * check if image is initialized
	 * it is initialized if it's not missing and if it's not loading
	 * 
	 * @return boolean
	 */
	public boolean isInitialized() {
		return (missing != null) && (loading != null);
	}

	/**
	 * 
	 * Set an image that will be shown instead of required one if
	 * missing
	 * 
	 * @param bitmap
	 */
	public void setMissingBitmap(Bitmap bitmap) {
		missing = bitmap;
	}
	
	/**
	 * 
	 * Set an image that will be shown instead of required one if
	 * loading
	 * 
	 * @param loading
	 */
	public void setLoadingBitmap(Bitmap loading) {
		this.loading = loading;
	}
	
	/**
	 * 
	 * setter method for connection timeout
	 * 
	 * @param connTimeout
	 */
	public void setConnTimeout(int connTimeout) {
		this.connTimeout = connTimeout;
	}
	
	/**
	 * 
	 * setter method for read timeout
	 * 
	 * @param readTimeout
	 */
	public void setReadTimeout(int readTimeout) {
		this.readTimeout = readTimeout;
	}
	
	
	/**
	 * 
	 * get image from specified url
	 * 
	 * @param url
	 * @return bitmap
	 */
	public Bitmap get(String url) {
		return urlToBitmap.get(url);
	}
	
	/**
	 * 
	 * loads image to specified imageView from given url,
	 * image is cached by default
	 * 
	 * @param image
	 * @param url
	 */
	public void load(ImageView image, String url) {
		load(image, url, true);
	}

	/**
	 * 
	 * loads image to specified imageView from given url,
	 * image cache is optional
	 * if url is empty, missingBitmap bil be shown
	 * if image is loading, temporary image for loading state
	 * will be shown until image is loaded
	 * 
	 * @param imageView
	 * @param url
	 * @param cache
	 */
	public void load(ImageView imageView, String url, boolean cache) {
		if (imageView == null) {
			Log.w(Ictest.class.getName(), "ImageView not specified!");
			return;
		} else if (TextUtils.isEmpty(url)) {
			imageView.setImageBitmap(missing);
			return;
		}

		Bitmap bitmap = urlToBitmap.get(url);
		if (bitmap != null) {
			imageView.setImageBitmap(bitmap);
		} else {
			imageView.setImageBitmap(loading);
			queue(imageView, url, cache);
		}
	}

	/**
	 * 
	 * queue for loading images, starts loading next picture when queued
	 * 
	 * @param imageView
	 * @param url
	 * @param cache
	 */
	public void queue(ImageView imageView, String url, boolean cache) {
		synchronized (queue) {
			Iterator<Group> it = queue.iterator();
			while (it.hasNext()) {
				if (it.next().getImageView().equals(imageView)) {
					it.remove();
					break;
				}
			}

			queue.add(new Group(imageView, url, cache));
		}

		loadNext();
	}

	/**
	 * 
	 * loads next image also double checks image availability and starts downloadThread
	 */
	private void loadNext() {
		synchronized (queue) {
			Iterator<Group> it = queue.iterator();
			if (!busy && it.hasNext()) {
				busy = true;
				Group group = it.next();
				it.remove();

				/* double check image availability */
				Bitmap bitmap = urlToBitmap.get(group.getUrl());
				if (bitmap != null) {
					group.getImageView().setImageBitmap(bitmap);
					busy = false;
					loadNext();
				} else {
					downloadThread = new DownloadThread(group);
					downloadThread.start();
				}

			}
		}
	}
	/**
	 * 
	 * sets image when loaded and starts download of next image
	 */
	protected void onLoad() {
		if (downloadThread != null) {
			Group group = downloadThread.getGroup();
			if (group.getBitmap() != null) {
				group.getImageView().setImageBitmap(group.getBitmap());
			} else if (missing != null) {
				group.getImageView().setImageBitmap(missing);
			}

			if (group.isCache() && (group.getBitmap() != null)) {
				urlToBitmap.put(group.getUrl(), group.getBitmap());
			}
		}
		downloadThread = null;
		busy = false;
		loadNext();
	}
	
	/**
	 * 
	 * clears queue and disconnects downloadThread
	 */
	public void cancel() {
		clearQueue();
		if (downloadThread != null) {
			downloadThread.disconnect();
			downloadThread = null;
		}
	}
	
	/**
	 * 
	 * clears queue by overwriting it with new LinkedList
	 */
	public void clearQueue() {
		synchronized (queue) {
			queue = new LinkedList<Group>();
		}
	}
	
	/**
	 * clears cache by overwriting it with new CacheMap
	 */
	public void clearCache() {
		synchronized (((CacheMap<String, Bitmap>) urlToBitmap).getSyncObject()) {
			urlToBitmap = new CacheMap<String, Bitmap>(Ictest.CACHE_SIZE, Ictest.CACHE_MODE);
		}
	}

	/**
	 * 
	 * Class for downloading images and saving them in group class
	 * 
	 * @author Tomica Bosnjak
	 *
	 */
	private class DownloadThread extends Thread {

		private final Handler threadHandler = new Handler();
		private final Runnable threadCallBack = new Runnable() {

			public void run() {
				onLoad();
			}
		};
		private HttpURLConnection connection = null;
		private Group group = null;

		public DownloadThread(Group group) {
			this.group = group;
		}

		@Override
		public void run() {
			InputStream inStream = null;
			try {
				connection = (HttpURLConnection) new URL(group.getUrl()).openConnection();
				connection.setConnectTimeout(connTimeout);
				connection.setReadTimeout(readTimeout);
				connection.setDoInput(true);
				connection.connect();

				inStream = connection.getInputStream();
				group.setBitmap(BitmapFactory.decodeStream(inStream));
			} catch (Throwable t) {
				Log.e(Ictest.class.getName(), t.getMessage(), t);
			} finally {
				if (inStream != null) {
					try {
						inStream.close();
					} catch (Exception ex) {
						// DO nothing
					}
				}
				if (connection != null) {
					connection.disconnect();
				}
			}

			threadHandler.post(threadCallBack);
		}

		public void disconnect() {
			if (connection != null) {
				connection.disconnect();
			}
		}

		public Group getGroup() {
			return group;
		}
	}

	/**
	 * 
	 * Image group, holds all images associated to gallery group
	 * 
	 * @author Tomica Bosnjak
	 *
	 */
	private class Group {

		private ImageView imageView = null;
		private String url = null;
		private Bitmap bitmap = null;
		private boolean cache;

		public Group(ImageView imageView, String url, boolean cache) {
			this(imageView, url, null, cache);
		}

		public Group(ImageView imageView, String url, Bitmap bitmap, boolean cache) {
			this.imageView = imageView;
			this.url = url;
			this.bitmap = bitmap;
			this.cache = cache;
		}

		public ImageView getImageView() {
			return imageView;
		}

		public String getUrl() {
			return url;
		}

		public Bitmap getBitmap() {
			return bitmap;
		}

		public void setBitmap(Bitmap bitmap) {
			this.bitmap = bitmap;
		}

		public boolean isCache() {
			return cache;
		}
	}
}
